package org.coco.mybatis.proxy;

import com.coco.entity.People;
import lombok.extern.slf4j.Slf4j;
import org.coco.mybatis.Configuration;
import org.coco.mybatis.binding.MethodSignature;
import org.coco.mybatis.mapping.BoundSql;
import org.coco.mybatis.mapping.MappedStatement;
import org.coco.mybatis.mapping.ResultMap;
import org.coco.mybatis.session.DefaultSqlSession;
import org.coco.mybatis.session.SqlSession;
import org.coco.mybatis.sql.SqlSource;
import org.coco.util.ValidationUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class MapperMethod {


    private static final String INT_TYPE = "int";
    private static final String VOID_TYPE = "void";
    private static final Configuration configuration;

    static {
        configuration = Configuration.getInstance();
    }

    public Object execute(Method method,String key, String methodName, Object[] args) {
        //从全局配置文件中找到该方法对应的MappedStatement
        Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();
        if(!mappedStatementMap.containsKey(key)) {
            log.error("没有找到的MapperStatement文件呢");
            throw new RuntimeException("没有与之对应的namespace的值");
        }
        MappedStatement mappedStatement = mappedStatementMap.get(key);
        List<SqlSource> sqlSourceList = mappedStatement.getSqlSourceList();
        Object result = null;
        if(!ValidationUtil.isEmpty(sqlSourceList)) {
            int length = sqlSourceList.size();
            SqlSource sqlSource = null;
            String sqlSourceId = key + "." + methodName;
            boolean isExist = false;
            for(int i = 0; i < length && !isExist; i ++) {
                sqlSource = sqlSourceList.get(i);
                if(sqlSourceId.equals(sqlSource.getId())) {
                    isExist = true;
                    continue;
                }
            }
            if(isExist) {
                //说明配置文件有对应的方法
                //判断出该sql语句是select还是update，还是delete或者是insert
                String sqlType = sqlSource.getSqlType();
                try {
                    result = doExecute(mappedStatement,method,sqlType,sqlSource,args);
                }catch (Exception e ){

                }
            }else {
                System.err.println("没有在xml配置文件中找到【"+method.getName()+"】方法");
                throw new RuntimeException("没有在xml配置文件中找到【"+method.getName()+"】方法");
            }
        }
        return result;
    }

    private Object doExecute(MappedStatement mappedStatement,Method method,String sqlType,SqlSource sqlSource,Object[] args) throws Exception{
        Object result = null;
        if("select".equals(sqlType)) {
            //是查询语句,因为查询语句有可能返回一个元素，又可能返回对个元素
            Class<?> returnType = method.getReturnType();
            List<MethodSignature> methodSignatures = null;
            //说明是返回多个结果
            if(returnsMany(returnType)) {
                //开始解析参数，解析好的参数放到一个集合中去
                methodSignatures = MethodSignature.resolveParamsType(args, method);
                result = executeForMany(mappedStatement,method,sqlSource,methodSignatures);
            }else {
                methodSignatures = MethodSignature.resolveParamsType(args, method);
                result = executeForOne(mappedStatement,method,sqlSource,methodSignatures);
            }
        }else if("update".equals(sqlType)) {

        }else if("delete".equals(sqlType)) {
            List<MethodSignature> methodSignatures = MethodSignature.resolveParamsType(args, method);
            result = deleteExecute(mappedStatement,method,sqlSource,methodSignatures);
        }else if("insert".equals(sqlType)) {
            List<MethodSignature> methodSignatures = MethodSignature.resolveParamsType(args, method);
            if(methodSignatures != null && !methodSignatures.isEmpty()) {
                //新增数据的时候获取需要保存对象
                result = this.insertExecute(mappedStatement,method,sqlSource,methodSignatures.get(0));
            }else {
                throw new RuntimeException("params is error");
            }
        }else {
            throw new RuntimeException(sqlType + ": 是未知的sql标签");
        }
        return result;
    }

    private Object executeForMany(MappedStatement mappedStatement,Method method, SqlSource sqlSource, List<MethodSignature> methodSignatures) throws Exception{

        //因为返回是一个List，所以需要判断List里面的泛型和xml配置的resultType的类型是否一样
        String returnType = method.getGenericReturnType().toString();
        //获取List里面的泛型
        String substring = null;
        //这里主要判断下List里面有没有填写泛型,如果没有写那么长度就是等于 24
        if(returnType.length() != 24) {
            substring = returnType.substring(15, returnType.length() - 1);
        }

        //获取xml配置的resultType
        String returnType1 = sqlSource.getReturnType();
        String returnMap = sqlSource.getReturnMap();
        ResultMap valueResultMap = null;
        if(!ValidationUtil.isEmpty(substring)) {
            if(!ValidationUtil.isEmpty(returnType1)) {
                if(!substring.equals(returnType1)) {
                    System.err.println("方法定义的返回值类型和xml设置的resultType返回类型不一致");
                    throw new RuntimeException("方法定义的返回值类型和xml设置的resultType返回类型不一致");
                }
            }else if(!ValidationUtil.isEmpty(returnMap)){
                //根据resultMap的值，获取ResultMap的type的值
                Map<String, ResultMap> resultMap = mappedStatement.getResultMap();
                valueResultMap = resultMap.get(returnMap);
                String type = valueResultMap.getType();
                if(!substring.equals(type)) {
                    System.err.println("方法定义的返回值类型和xml设置的resultMap返回类型不一致");
                    throw new RuntimeException("方法定义的返回值类型和xml设置的resultMap返回类型不一致");
                }
            }else {
                System.err.println("xml配置应该设置resultMap或者resultType");
                throw new RuntimeException("xml配置应该设置resultMap或者resultType");
            }
        }else {
            //说明List没有指定类型
            Map<String, ResultMap> resultMap = mappedStatement.getResultMap();
            valueResultMap = resultMap.get(returnMap);
        }

        BoundSql boundSql = new BoundSql();
        BoundSql boundSqlValue = boundSql.parseSqlBySqlSource(sqlSource, methodSignatures);
        SqlSession sqlSession = new DefaultSqlSession();
        Class<?> aClass = null;
        if(!ValidationUtil.isEmpty(returnType1)) {
            try {
                aClass = Class.forName(returnType1);
            }catch (Exception e) {
                System.err.println("class not found");
                throw new RuntimeException("class not found");
            }
        }
        boundSqlValue.setClazz(aClass);
        boundSqlValue.setResultMap(valueResultMap);
        List<Object> list = sqlSession.selectList(boundSqlValue);
        return list;
    }

    private Object executeForOne(MappedStatement mappedStatement,Method method, SqlSource sqlSource, List<MethodSignature> methodSignatures) throws Exception{
        //获取方法返回的对象类型
        String methodReturnType = method.getGenericReturnType().toString();
        Class methodReturnTypeClass = method.getReturnType();
        //获取xml配置的resultType
        String resultType = sqlSource.getReturnType();
        //获取xml配置的resultMap
        String resultMap = sqlSource.getReturnMap();
        ResultMap valueResultMap = null;
        if(!ValidationUtil.isEmpty(resultType)) {
            //xml配置了resultType
            Class resultTypeClass = Class.forName(resultType);
            if(!resultTypeClass.getName().equals(methodReturnTypeClass.getName())) {
                System.err.println("xml配置的返回对象类型于方法返回的对象类型不匹配");
                throw new RuntimeException("xml配置的返回对象类型于方法返回的对象类型不匹配");
            }
        }else if(!ValidationUtil.isEmpty(resultMap)){
            //xml配置resultMap
            //根据resultMap的值，获取ResultMap的type的值
            Map<String, ResultMap> resultMapValue = mappedStatement.getResultMap();
            valueResultMap = resultMapValue.get(resultMap);
            String type = valueResultMap.getType();
            if(!type.equals(methodReturnType)) {
                System.err.println("xml配置的返回对象类型于方法返回的对象类型不匹配");
                throw new RuntimeException("xml配置的返回对象类型于方法返回的对象类型不匹配");
            }
        }
        BoundSql boundSql = new BoundSql();
        BoundSql boundSqlValue = boundSql.parseSqlBySqlSource(sqlSource, methodSignatures);
        SqlSession sqlSession = new DefaultSqlSession();
        Class<?> aClass = null;
        if(!ValidationUtil.isEmpty(resultType)) {
            try {
                aClass = Class.forName(resultType);
            }catch (Exception e) {
                System.err.println("class not found");
                throw new RuntimeException("class not found");
            }
        }
        boundSqlValue.setClazz(aClass);
        boundSqlValue.setResultMap(valueResultMap);
        List<Object> list = sqlSession.selectList(boundSqlValue);
        if(list.size() > 1) {
            throw new RuntimeException("result to many");
        }
        return list.get(0);
    }

    private Integer deleteExecute(MappedStatement mappedStatement,Method method, SqlSource sqlSource, List<MethodSignature> methodSignatures) {
        String typeName = method.getGenericReturnType().getTypeName();
        if(!INT_TYPE.equals(typeName)) {
            throw new RuntimeException("method return type have to int");
        }
        BoundSql boundSql = new BoundSql();
        BoundSql boundSqlValue = boundSql.parseSqlBySqlSource(sqlSource, methodSignatures);
        SqlSession sqlSession = new DefaultSqlSession();
        Integer delete = sqlSession.delete(boundSqlValue);
        return delete;
    }

    private Integer insertExecute(MappedStatement mappedStatement,Method method, SqlSource sqlSource, MethodSignature methodSignature) {
        String typeName = method.getGenericReturnType().getTypeName();
        if(!INT_TYPE.equals(typeName) && !VOID_TYPE.equals(typeName)) {
            throw new RuntimeException("method return type have to int");
        }
        String parameterType = sqlSource.getParameterType();
        Class<?> clazz = methodSignature.getClazz();
        String name = clazz.getName();
        if(!parameterType.equals(name)) {
            throw new RuntimeException("parameterType not equals params type");
        }
        Object value = methodSignature.getValue();
        if(!clazz.isInstance(value)) {
            throw new RuntimeException("object class is error");
        }
        Map<String,List> fieldValueMap = new HashMap<>();
        Field[] declaredFields = clazz.getDeclaredFields();
        if(declaredFields.length > 0) {
            List<Object> list = null;
            for(Field field : declaredFields) {
                list = new ArrayList<>();
                String fieldName = field.getName();
                Class<?> typeClazz = field.getType();
                field.setAccessible(true);
                Object fieldValue = null;
                try{
                    fieldValue = field.get(value);
                }catch (Exception e) {}
                list.add(fieldValue);
                list.add(typeClazz.getName());
                fieldValueMap.put(fieldName,list);
            }
        }
        List<MethodSignature> methodSignatures = new ArrayList<>();
        if(!fieldValueMap.isEmpty()) {
           MethodSignature methodSignature1 = null;
           for(String key : fieldValueMap.keySet()) {
               methodSignature1 = new MethodSignature();
               List list = fieldValueMap.get(key);
               methodSignature1.setClazz(list.get(1).getClass());
               methodSignature1.setName(key);
               methodSignature1.setValue(list.get(0));
               methodSignatures.add(methodSignature1);
           }
        }
        BoundSql boundSql = new BoundSql();
        BoundSql boundSqlValue = boundSql.parseSqlBySqlSource(sqlSource, methodSignatures);
        SqlSession sqlSession = new DefaultSqlSession();
        Integer insert = sqlSession.insert(boundSqlValue);
        return insert;
    }

    private boolean returnsMany(Class clazz) {
        if(clazz == List.class) {
            return true;
        }else {
            return false;
        }
    }

    public static  <T> T get(Class<T> clz,Object o){
        if(clz.isInstance(o)){
            return clz.cast(o);
        }
        return null;
    }
}
